// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Simple wrapper and basic configuration of Google breakpad.  We try
// to avoid using any unnecessary code (like libbase) to make it as
// non-intrusive as possible to link in this library.

#include <errno.h>
#include <pwd.h>
#include <stddef.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>

#include "client/linux/handler/exception_handler.h"
#include "common/linux/linux_libc_support.h"
#include "common/linux/linux_syscall_support.h"
#include "crash-dumper/crash_dumper.h"

// Define sys_mkdir (sys_open passes mode_t as int, so we do too).
LSS_INLINE _syscall2(int, mkdir, const char *, pathname, int, mode);

static const char kDefaultUser[] = "chronos";
static const char kSystemCrashParentPath[] = "/var/spool";
static const char kSystemCrashPath[] = "/var/spool/crash";
static const char kUserCrashParentPath[] = "/home/chronos/user";
static const char kUserCrashPath[] = "/home/chronos/user/crash";

// Pointers to paths, set when crash dumping is enabled.
static const char *s_crash_path;
static const char *s_crash_parent_path;
static mode_t s_dump_directory_mode;

static bool s_any_crashes_occurred = false;
static google_breakpad::ExceptionHandler *s_breakpad_handler;

static bool s_enabled = false;

// Use CHECK_CONDITION to abort if a condition is met - the condition
// should only be a condition that would fail if a program was written
// incorrectly.  Use REPORT_ERROR for conditions that are legal run-time
// error conditions.
#define CHECK_CONDITION(_cond, _message) \
  do { if (!(_cond)) { fputs(_message"\n", stderr); abort(); } } while(false)

// Use REPORT_ERROR to report a run-time condition (via syslog) that should
// not abort the entire program running, but still indicate a failure to
// be investigated.
#define REPORT_ERROR(_message) \
  do { syslog(LOG_ERR, _message); fputs(_message"\n", stderr); } while(false)

// This static object will cause anything that links in this object to get
// crash handling for the duration of this file's scope.
static CrashDumper g_crash_dumper;


// Prepare the crash path.  Must avoid allocating memory.
static bool PrepareCrashPath() {
  struct kernel_stat buf;
  if (sys_stat(s_crash_path, &buf) < 0) {
    // Dump directory does not exist, so create it and its parent now,
    // at the time of the crash.
    sys_mkdir(s_crash_parent_path, 755);
    sys_mkdir(s_crash_path, s_dump_directory_mode);
  }
  return sys_stat(s_crash_path, &buf) == 0;
}

// Use FilterCallback to avoid recursive crashing.
// TODO(kmixter): Also use it to avoid enqueuing too many crash dumps
// system wide - if we get in a crash/restart loop we don't want the entire
// stateful partition to be filled up.
static bool FilterCallback(void *) {
  // This function runs in a compromised context - a crash has already
  // occurred so memory allocation and libc should be avoided.
  bool old_any_crashes_occured = s_any_crashes_occurred;
  s_any_crashes_occurred = true;
  // The crash path may have been removed or mounted-over, so make sure
  // there is a container directory.  If it fails, not much we can do.
  PrepareCrashPath();
  return !old_any_crashes_occured;
}

static bool GetEffectiveUser(std::string *result) {
  char storage[256];
  struct passwd passwd_storage;
  struct passwd *passwd_result = NULL;

  if (getpwuid_r(geteuid(), &passwd_storage, storage, sizeof(storage),
                 &passwd_result) != 0 || passwd_result == NULL) {
    return false;
  }

  *result = passwd_result->pw_name;
  return true;
}

void CrashDumper::Enable() {
  CHECK_CONDITION(!s_enabled, "Crash handling already enabled");

  std::string name;
  if (!GetEffectiveUser(&name)) {
    REPORT_ERROR("getpwuid_r failed, cannot enable crash reporting");
    return;
  }

  if (name == kDefaultUser) {
    // Crashes as "chronos" when the user is not yet logged in will
    // still be recorded to /home/chronos/user/crash in the
    // stateful partition (outside cryptohome).  These will eventually
    // be uploaded when no user is logged in.
    s_crash_path = kUserCrashPath;
    s_crash_parent_path = kUserCrashParentPath;
    s_dump_directory_mode = 0755;
  } else {
    s_crash_path = kSystemCrashPath;
    s_crash_parent_path = kSystemCrashParentPath;
    // Make the dump directory sticky so any UID can write to
    // it but not remove another UID's crashes.
    s_dump_directory_mode = 01777;
  }

  if (!PrepareCrashPath()) {
    REPORT_ERROR("Unable to create crash path");
    return;
  }

  // Begin collecting crashes
  s_breakpad_handler = new google_breakpad::ExceptionHandler(
      s_crash_path,
      FilterCallback,
      NULL,  // No minidump callback - sending happens asynchronously to writing
      NULL,  // No callback context necessary
      true);  // Install handler now.

  s_enabled = true;
}

bool CrashDumper::IsEnabled() {
  return s_enabled;
}

void CrashDumper::Disable() {
  CHECK_CONDITION(s_enabled, "Crash handling was not enabled");
  delete s_breakpad_handler;
  s_breakpad_handler = NULL;
  s_crash_path = NULL;
  s_crash_parent_path = NULL;
  s_enabled = false;
}
